package thing;

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

/**
 *
 * @author Tharen
 */
public final class WorldManager {

    private static ObservableList<Worlds> worlds = FXCollections.observableArrayList();
    private static Path libraryRoot;// = Paths.get("G:\\Dropbox\\Minecraft\\Worlds\\");
    private static Path serverRoot;// = Paths.get("C:\\Users\\Owner\\Desktop\\MC nexus defense server");
    private static Path installDir = Paths.get("C:" + File.separator + "Program Files" + File.separator + "MCSolarSystem");
    //private static OptionsMenu optnsMenu;// = new OptionsMenu(StageStyle.DECORATED);

    /**
     * Creates an instance of WorldManager. There should only be one of these,
     * though it might be possible to use 2 or more simultaneously.
     */
    public WorldManager() {
        init();
    }

    /**
     *
     * @return Returns all the worlds currently contained.
     *
     * NOTE: There can still be folders with worlds that are not in this list!
     */
    public ObservableList<Worlds> getWorlds() {
        return worlds;
    }

    /**
     * CURRENTLY: Working.
     *
     * Primary method for adding a world to the list.
     *
     * TODO: Exception handling. Lots of it.
     *
     * @param name String name of the world. This will be used for the
     * containing folder.
     * @param type String Type of map.
     * @param description String short description of the map. e.g.
     * mods,use,version,
     * @param image Location of the image to be displayed
     * @param modsRequires Boolean for whether or not mods are required to
     * properly run this map.
     */
    public void addWorld(String name, String type, boolean modsRequires, String image, String description, String world) {
        Path imgTemp = Paths.get(image);
        Path worldTemp = Paths.get(world);
        Worlds tempWorld = new Worlds(name, type, modsRequires, imgTemp.getFileName().toString(), description, worldTemp.getFileName().toString());

        if (worlds.contains(tempWorld)) {
            AppLauncher.toUser(name + " already contained in the worlds array.");
        } else {
            Path tempDirPath = Paths.get(libraryRoot + File.separator + name + File.separator);
            Path tempPath = Paths.get(tempDirPath + File.separator + name + "_index.txt");
            //System.out.print(tempPath);
            try {
                if (Files.exists(tempDirPath)) {
                    AppLauncher.toUser("Directory Exsists! Attempting index creation.");
                    if (Files.exists(tempPath)) {
                        /**
                         * TODO: Make an override current world option.
                         */
                        AppLauncher.toUser("World already exists on HDD. Adding to array.");
                        zipWorld(world, name);
                        picCopy(image, name);
                        worlds.add(tempWorld);
                    } else {
                        Files.createFile(tempPath);
                        zipWorld(world, name);
                        picCopy(image, name);
                        worlds.add(tempWorld);
                    }
                } else {
                    Files.createDirectory(tempDirPath);
                    Files.createFile(tempPath);
                    AppLauncher.toUser("Copying from " + world);
                    zipWorld(world, name);
                    picCopy(image, name);
                    worlds.add(tempWorld);
                }
                AppLauncher.toUser("World Added!");
                //System.out.println("World Added!");
            } catch (Exception e) {
                AppLauncher.toUser("Failed to add the world! Probably invalid path.");
                e.printStackTrace();
            }
        }
    }

    /**
     * CURRENTLY: Working.
     *
     * Replaced reading the worlds from a txt. It now gets them straight from
     * the dir. It currently will only add a world with a complete index file
     * with it.
     *
     * TODO: More exception handling.
     */
    public static void dirRead() {
        AppLauncher.toUser("Reading the given directory.");
        try {
            if (libraryRoot.toFile().exists()) {
                String[] fldrs = libraryRoot.toFile().list();
                for (int k = 0; k < fldrs.length; k++) {
                    try {
                        if (fldrs[k].contains("Archive")) {
                            k++;
                        }
                        Scanner temp = new Scanner(Paths.get(libraryRoot.toString() + File.separator + fldrs[k] + File.separator + fldrs[k] + "_index.txt"));
                        temp.useDelimiter(System.lineSeparator());
                        //System.out.println("Reading: " + libraryRoot.toString() + "\\" + fldrs[k] + "\\" + fldrs[k] + "_index.txt");
                        ArrayList<String> info = new ArrayList<>();
                        int i = 0;
                        while (temp.hasNext()) {
                            info.add(temp.next());
                            i++;
                        }
                        boolean x;
                        if (info.get(2).matches("true")) {
                            x = true;
                        } else {
                            x = false;
                        }
                        worlds.add(new Worlds(info.get(0), info.get(1), x, info.get(3), info.get(4), info.get(5)));
                    } catch (Exception e) {
                        AppLauncher.toUser("Failed to read: " + libraryRoot.toString() + File.separator + fldrs[k] + File.separator + fldrs[k] + "_index.txt");
                        e.printStackTrace();
                    }
                }
            } else {
                AppLauncher.toUser("Unable to find: " + libraryRoot.toString());
                try {
                    Files.createDirectories(libraryRoot);
                    AppLauncher.toUser("Specified library created.");
                } catch (Exception e) {
                    e.printStackTrace();
                    AppLauncher.toUser("Unable to create library: " + libraryRoot.toString());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * CURRENTLY: Working.
     *
     * Saves the values of the world objects in the observable array.
     *
     */
    public static void saveWorldValues() {
        //System.out.println(worlds.size());
        for (int i = 0; i < worlds.size(); i++) {
            System.out.println("Starting pass: " + i);
            String temp = worlds.get(i).getWorldName();
            File tempF = new File(libraryRoot + File.separator + temp + File.separator + temp + "_index.txt");
            System.out.println(tempF.getPath());
            try {
                OutputStreamWriter osw = new FileWriter(tempF, false);
                BufferedWriter out = new BufferedWriter(osw);
                out.write(worlds.get(i).getWorldName());
                out.newLine();
                out.write(worlds.get(i).getType());
                out.newLine();
                out.write(worlds.get(i).isModsRequired().toString());
                out.newLine();
                out.write(worlds.get(i).getImage());
                out.newLine();
                out.write(worlds.get(i).getDescription());
                out.newLine();
                out.write(worlds.get(i).getFilePath());
                out.newLine();
                out.flush();
            } catch (Exception e) {
                AppLauncher.toUser("Warning! Unable to save world values!");
                e.printStackTrace();
            }
        }
    }

    /**
     * CURRENTLY: Working. Deletes all files and subdirectories under dir.
     * Returns true if all deletions were successful. If a deletion fails, the
     * method stops attempting to delete and returns false.
     *
     * @param dir File to be deleted.
     * @return Returns true only if deletion was successful.
     */
    private static boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        // The directory is now empty so delete it
        return dir.delete();
    }

    /**
     * Deletes the selected world. CURRENTLY: Working.
     *
     * @param world World to be removed.
     */
    public static void removeWorld(Worlds world) {
        Path temp = Paths.get(libraryRoot.toString(), world.getWorldName());
        File fTemp = temp.toFile();
        if (fTemp.exists()) {
            if (deleteDir(fTemp)) {
                worlds.remove(world);
            } else {
                AppLauncher.toUser("Unable to delete world folder: " + fTemp.toString());
            }
        } else {
            worlds.remove(world);
        }
    }

    /**
     * Runs at creation of the world manager. Reads and sets the paths for the
     * server and world library
     *
     * CURRENTLY: WORKING.
     *
     * TODO: Add System specific information.
     */
    private void init() {
        try {
            Path temp = Paths.get(installDir + File.separator + "paths.txt");
            if (temp.toFile().exists()) {
                Scanner tempScnr = new Scanner(temp);
                tempScnr.useDelimiter(System.lineSeparator());
                if (tempScnr.hasNext()) {
                    libraryRoot = Paths.get(tempScnr.next());
                    if (tempScnr.hasNext()) {
                        serverRoot = Paths.get(tempScnr.next());
                    } else {
                        serverRoot = Paths.get(System.getProperty("user.home") + File.separator + "MCServer");
                        AppLauncher.toUser("Please assign a path for your MineCraft Server. Setting to default.");
                    }
                    dirRead();
                } else {
                    libraryRoot = Paths.get(System.getProperty("user.home") + File.separator + "WorldsLibrary");
                    serverRoot = Paths.get(System.getProperty("user.home") + File.separator + "MCServer");
                    AppLauncher.toUser("Please assign directories for your library"
                            + " and server locations in Options.");
                }
            } else {
                Files.createDirectories(installDir);
                Files.createFile(temp);
                AppLauncher.toUser("Please define where your Server is located "
                        + "and where you want the worlds to be stored in options.");
                AppLauncher.optnsMenu.show();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * CURRENTLY: WORKING.
     *
     * @return Returns the current libraryRoot directory.
     */
    public static String getRoot() {
        return libraryRoot.toString();
    }

    /**
     * Returns the server root as a path.
     *
     * @return LibraryRoot as a path
     */
    public static Path getLibRoot() {
        return libraryRoot;
    }

    /**
     * CURRENTLY: WORKING.
     *
     * @return returns the location of server
     */
    public static String getServerRoot() {
        return serverRoot.toString();

    }

    /**
     * Returns the serverRoot as a path
     *
     * @return Server Root as a path
     */
    public static Path getServerRootPath() {
        return serverRoot;
    }

    /**
     * CURRENTLY: WORKING. Sets the roots to the specified location and saves
     * them to the install dir.
     *
     * @param libraryRootarg Directory where the Worlds should be saved.
     * @param serverRootarg Directory where the minecraft server is located.
     */
    public static void setRoots(String libraryRootarg, String serverRootarg) {
        if (libraryRootarg == null || "".equals(libraryRootarg)) {
        } else {
            libraryRoot = Paths.get(libraryRootarg);
        }
        if (serverRootarg == null || serverRootarg.equals("")) {
        } else {
            serverRoot = Paths.get(serverRootarg);
        }
        Path temp = Paths.get(installDir + File.separator + "paths.txt");
        try {
            OutputStreamWriter osw = new FileWriter(temp.toFile(), false);
            BufferedWriter out = new BufferedWriter(osw);
            out.write(libraryRootarg);
            out.newLine();
            out.write(serverRootarg);
            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
            AppLauncher.toUser("Unable to save root path location(s)!");
        }
    }

    /**
     * Zips and copies the world given to the library.
     *
     *
     * CURRENTLY: WORKING
     *
     * TODO: Exception Handling here.
     *
     * @param worldPath Path to the world that will be zipped and moved to the
     * archive.
     * @param worldName Name of the world being zipped. This is used to put the
     * zipped file in the correct world folder.
     */
    public static void zipWorld(String worldPath, String worldName) {
        AppLauncher.toUser("Attempting zip.");
        final Path path = Paths.get(worldPath);
        if (!path.toFile().exists()) {
            AppLauncher.toUser("Invalid world path.");
            return;
        }
        try {
            FileOutputStream fos;
            if ("Archive".equals(worldName)) {
                AppLauncher.toUser("Archiving old world.");
                int month = Calendar.MONTH + 1;
                String datePath = getRoot() + File.separator + worldName + File.separator + getServerProperty(3)+ month +  "-" + Calendar.DATE + "-" + Calendar.YEAR + ".zip";
                File temp = new File(datePath);
                temp.getParentFile().mkdirs();
                fos = new FileOutputStream(datePath);
            } else {
                File temp = new File(getRoot() + File.separator + worldName + File.separator + "World.zip");
                temp.getParentFile().mkdirs();
                fos = new FileOutputStream(temp);
            }

            final ZipOutputStream zip = new ZipOutputStream(new BufferedOutputStream(fos));
            Files.walkFileTree(path, new SimpleFileVisitor<Path>() {

                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    try {
                        zip.putNextEntry(new ZipEntry(file.subpath(path.getNameCount() - 1, file.getNameCount()).toString()));
                    } catch (Exception e) {
                        AppLauncher.toUser("Unable to zip world!");
                        e.printStackTrace();
                        System.out.println(file);
                        return FileVisitResult.TERMINATE;
                    }
                    return FileVisitResult.CONTINUE;
                }
            });
            zip.flush();
            zip.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * CURRENTLY: WORKING.
     *
     * @param imgLoc Location of the picture to be copied.
     * @param worldName Name of the world the picture is for.
     */
    private void picCopy(String imgLoc, String worldName) {
        Path imgPath = Paths.get(imgLoc);

        Path imgDest = Paths.get(libraryRoot + File.separator + worldName + File.separator + imgPath.getFileName().toString());
        if (imgPath.toFile().exists()) {
            try {
                Files.copy(imgPath, imgDest);
            } catch (IOException ex) {
                AppLauncher.toUser("Failed to move the given image!");
                Logger.getLogger(WorldManager.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            AppLauncher.toUser("Either no picture was given or the path was invalid.");
        }
    }

    /**
     * CURRENTLY: Working
     *
     * Copies the world folder to the server for play.
     *
     * TODO: Inspect this.
     *
     * @param worldName Name of world to be copied.
     */
    public static void extractWorld(String worldName) {
        final int BUFFER = 2048;
        File aTemp = new File(serverRoot.toString() + File.separator + getServerProperty(3));
        if (aTemp.exists()) {
            zipWorld(aTemp.getPath(), "Archive");
            if (aTemp.delete()) {
                System.out.println("Delete Success.");
            }else System.out.println("Delete Failure.");
        }
        try {
            BufferedOutputStream dest = null;
            FileInputStream fis = new FileInputStream(libraryRoot + File.separator + worldName + File.separator + "World.zip");
            ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                int count;
                byte data[] = new byte[BUFFER];
                // write the files to the disk
                File temp = new File(serverRoot + File.separator + entry.getName());
                temp.getParentFile().mkdirs();
                FileOutputStream fos = new FileOutputStream(temp);
                dest = new BufferedOutputStream(fos, BUFFER);
                while ((count = zis.read(data, 0, BUFFER))
                        != -1) {
                    dest.write(data, 0, count);
                }
                dest.flush();
                dest.close();
            }
            zis.close();
            AppLauncher.toUser("Load Complete.");
        } catch (FileNotFoundException fn) {
            AppLauncher.toUser("Load Failed.");
            fn.printStackTrace();
        } catch (Exception e) {
            AppLauncher.toUser("Load Failed and I don't know why.");
            e.printStackTrace();
        }

    }
    
    /**Returns the property from the requested row.
     * 
     * @param row The row from which you want the property. Level name is 3.
     * @return returns the found property.
     */
    public static String getServerProperty(int row){
        File serverProps = new File(serverRoot.toString() + File.separator + "server.properties");
        try {
            Scanner scnr = new Scanner(serverProps);
            scnr.useDelimiter(System.lineSeparator());
            //AppLauncher.toUser(scnr.findWithinHorizon("level-name", 0));
            ArrayList<String> temp = new ArrayList<>();
            while (scnr.hasNext()) {
                temp.add(scnr.next());
            }
                    String[] sTemp = temp.get(row).split("=");
                    //AppLauncher.toUser(sTemp[0]);
                    //AppLauncher.toUser(sTemp[1]);
                    return sTemp[1];
        } catch (FileNotFoundException ex) {
            AppLauncher.toUser("Unable to find server properties file.");
            ex.printStackTrace();
            return "Unknown";
        } catch (Exception e){
            e.printStackTrace();
            AppLauncher.toUser("Exception finding server property.");
            return "Unknown";
        }
    }
    
    /**
     * BULLSHIT
     *
     */
    public static void test() {
        File serverProps = new File(serverRoot.toString() + File.separator + "server.properties");
        try {
            FileWriter writer = new FileWriter(serverProps);
            
        } catch (IOException ex) {
            AppLauncher.toUser("Unable to change world name to the correct name in server properties.");
        }
    }
}
